#pragma once

#include <wrl.h>
#include <vector>

namespace DXBase
{
	// Helper class for basic timing.
	[Windows::UI::Xaml::Data::Bindable]
    [Windows::Foundation::Metadata::WebHostHidden]
	public ref class PerformanceTimer sealed :  Windows::UI::Xaml::Data::INotifyPropertyChanged
	{
	public:
		// Initializes internal timer values.
		PerformanceTimer()
		{
			if (!QueryPerformanceFrequency(&m_frequency))
				throw ref new Platform::FailureException();
			Reset();
		}

		property bool Paused
		{
			bool get() { return mPaused; }
			void set(bool value)
			{
				if (value == mPaused)
					return;
				if (value)
				{
					if (!QueryPerformanceCounter(&m_pauseTime))
						throw ref new Platform::FailureException();
				}
				else
				{
					LARGE_INTEGER currentTime;
					if (!QueryPerformanceCounter(&currentTime))
						throw ref new Platform::FailureException();
					pausedtime += currentTime.QuadPart - m_pauseTime.QuadPart;
				}
				mPaused = value;
				OnPropertyChanged("Paused");
			}
		}
	
		// Reset the timer to initial values.
		void Reset()
		{
			QueryPerformanceCounter(&m_startTime);
			m_lastTime = m_startTime;
			mPaused = false;
			m_total = 0.0f;
			m_delta = 1.0f / 60.0f;
			pausedtime = 0;
			frames.clear();

			OnPropertyChanged("Total");
			OnPropertyChanged("Delta");
			OnPropertyChanged("FPS");
			OnPropertyChanged("Paused");
		}

		// Duration in seconds between the last call to Reset() and the last call to Update().
		property float Total { float get() { return m_total; } }
	
		// Duration in seconds between the previous two calls to Update().
		property float Delta { float get() { return m_delta; } }
	
		// the number of time Update() has been called in the last second
		property int FPS { int get() { return (int)frames.size(); } }

		// Update the timer's internal values.
		void Update()
		{
			if (mPaused)
				return;

			LARGE_INTEGER currentTime;
			if (!QueryPerformanceCounter(&currentTime))
				throw ref new Platform::FailureException();
		
			m_total = TimeDiff(currentTime, m_startTime);
			if (m_lastTime.QuadPart == m_startTime.QuadPart)
			{
				// If the timer was just reset, report a time delta equivalent to 60Hz frame time.
				m_delta = 1.0f / 60.0f;
			}
			else
			{
				m_delta = TimeDiff(currentTime, m_lastTime);
			}
		
			m_lastTime = currentTime;
			AddCurrentFrame();

			OnPropertyChanged("Total");
			OnPropertyChanged("Delta");
			OnPropertyChanged("FPS");
		}

#include "MACROs_DEF.h"
		PROPERTY_CHANGE_EVENT
#include "MACROs_UNDEF.h"

	private:
		LARGE_INTEGER m_frequency;
		LONGLONG pausedtime;

		LARGE_INTEGER m_startTime;
		LARGE_INTEGER m_lastTime;
		LARGE_INTEGER m_pauseTime;

		float m_total;
		float m_delta;

		bool mPaused;

		std::vector<LARGE_INTEGER> frames;

		float TimeDiff(LARGE_INTEGER t1, LARGE_INTEGER t0)
		{
			return static_cast<float>(
				static_cast<double>(t1.QuadPart - t0.QuadPart - pausedtime) /
				static_cast<double>(m_frequency.QuadPart)
				);
		}
		void AddCurrentFrame()
		{
			size_t tooOld = 0;
			for (size_t i=0, N=frames.size(); i<N; i++)
			{
				auto prev = frames[i];
				auto since = TimeDiff(m_lastTime, prev);
				if (since <= 1)
					break;
				tooOld = i+1;
			}
			if (tooOld > 0)
				frames.erase(frames.begin(), frames.begin() + tooOld);
			frames.push_back(m_lastTime);
		}
	};
}